package ArithmeticObjects;
import java.io.IOException;

import utils.ByteTree;

/**
 * Representing an element in a field
 * @author Idan
 */
public class FieldElement extends Element {

	private Field Zq;
	
	/**
	 * creates a FieldElement from a given ByteTree representing it and a Field indicating its order
	 * @param bt a ByteTree representing the FieldElement
	 * @param Zq a Group indicating its order
	 */
	public FieldElement(ByteTree bt, Field Zq){
		value = bt.getNumber().mod(Zq.getOrder().getValue());
		this.Zq = Zq;
	}
	
	/**
	 * creates a FieldElement from a given LargeNumber representing it and a Group indicating its order
	 * @param n the LargeNumber representing the FieldElement
	 * @param Zq the Group indicating its order
	 * @throws Exception in case the construction failed
	 */
	public FieldElement(LargeNumber n, Field Zq) throws Exception{
		value = n;
		this.Zq = Zq;
	}

	@Override
	public Element add(Element elementA,
			Element... elements) throws Exception{
		if (!(elementA instanceof FieldElement))
			throw new Exception("Wrong class, Expected: FieldElement, got: " + elementA.getClass().getName());
		LargeNumber val = value.add(elementA.getValue()).mod(Zq.getOrder().getValue());
		for (int i=0; i<elements.length; i++)
		{
			if (!(elements[i] instanceof FieldElement))
				throw new Exception("Wrong class, Expected: FieldElement, got: " + elements[i].getClass().getName());
			val = val.add(elements[i].getValue()).mod(Zq.getOrder().getValue());
		}
		return new FieldElement(val, Zq);
	}

	@Override
	public Element subtract(Element elementA) throws Exception{
		if (!(elementA instanceof FieldElement))
			throw new Exception("Wrong class, Expected: FieldElement, got: " + elementA.getClass().getName());
		LargeNumber val = value.subtract(elementA.getValue()).mod(Zq.getOrder().getValue());
		return new FieldElement(val, Zq);
	}
	
	@Override
	public Element multiply(Element elementA,
			Element... elements) throws Exception {
		if (!(elementA instanceof FieldElement))
			throw new Exception("Wrong class, Expected: FieldElement, got: " + elementA.getClass().getName());	
		LargeNumber val = value.multiply(elementA.getValue()).mod(Zq.getOrder().getValue());
		for (int i=0; i<elements.length; i++)
		{
			if (!(elements[i] instanceof FieldElement))
				throw new Exception("Wrong class, Expected: FieldElement, got: " + elements[i].getClass().getName());
			val = val.multiply(elements[i].getValue()).mod(Zq.getOrder().getValue());
		}
		return new FieldElement(val, Zq);
	}
	
	@Override
	public Element divide(Element elementA) throws Exception {
		if (!(elementA instanceof FieldElement))
			throw new Exception("Wrong class, Expected: FieldElement, got: " + elementA.getClass().getName());
		LargeNumber val = new LargeNumber(value.multiply(elementA.getValue().
				modInverse(Zq.getOrder().getValue()))).mod(Zq.getOrder().getValue());
		return new FieldElement(val, Zq);
	}
	
	@Override
	public Element power(Element exponent) throws Exception {
		LargeNumber val = value.modPow(exponent.getValue(), Zq.getOrder().getValue());
		return new FieldElement(val, Zq);
	}
	
	@Override
	public Element additiveInverse() throws Exception {
		return new FieldElement(Zq.getOrder().getValue().subtract(this.getValue()),Zq);
	}
	
	@Override
	public int compareTo(Element elementA) throws Exception {
		if (!(elementA instanceof FieldElement))
			throw new Exception("Wrong class, Expected: FieldElement, got: " + elementA.getClass().getName());
		return value.compareTo(elementA.getValue());
	}

	/**
	 * @return the FieldElement's order
	 */
	public Element getOrder()
	{
		return Zq.getOrder();
	}

	@Override
	public String toString(){
		return value.toString();
	}

	@Override
	public ByteTree getByteTree() throws IOException {
		return ByteTree.CreateFixedSizeLeaf(value.toByteArray(),(Zq.getOrder().getValue().bitLength()+7)/8);
	}
}
